// (c) 2019 Dapper Labs - ALL RIGHTS RESERVED

package network

import (
	"context"
	"time"

	blocks "github.com/ipfs/go-block-format"
	"github.com/ipfs/go-cid"
	"github.com/libp2p/go-libp2p-core/peer"

	"github.com/onflow/flow-go/model/flow"
	"github.com/onflow/flow-go/module/component"
	"github.com/onflow/flow-go/network/message"
)

// Topic is the internal type of Libp2p which corresponds to the Channel in the network level.
// It is a virtual medium enabling nodes to subscribe and communicate over epidemic dissemination.
type Topic string

func (t Topic) String() string {
	return string(t)
}

// Middleware represents the middleware layer, which manages the connections to
// our direct neighbours on the network. It handles the creation & teardown of
// connections, as well as reading & writing to/from the connections.
type Middleware interface {
	component.Component

	// SetOverlay sets the overlay used by the middleware. This must be called before the middleware can be Started.
	SetOverlay(Overlay)

	// Dispatch sends msg on a 1-1 direct connection to the target ID. It models a guaranteed delivery asynchronous
	// direct one-to-one connection on the underlying network. No intermediate node on the overlay is utilized
	// as the router.
	//
	// Dispatch should be used whenever guaranteed delivery to a specific target is required. Otherwise, Publish is
	// a more efficient candidate.
	SendDirect(msg *message.Message, targetID flow.Identifier) error

	// Publish publishes a message on the channel. It models a distributed broadcast where the message is meant for all or
	// a many nodes subscribing to the channel. It does not guarantee the delivery though, and operates on a best
	// effort.
	Publish(msg *message.Message, channel Channel) error

	// Subscribe subscribes the middleware to a channel.
	Subscribe(channel Channel) error

	// Unsubscribe unsubscribes the middleware from a channel.
	Unsubscribe(channel Channel) error

	// Ping pings the target node and returns the ping RTT or an error
	Ping(targetID flow.Identifier) (message.PingResponse, time.Duration, error)

	// UpdateAllowList fetches the most recent identity of the nodes from overlay
	// and updates the underlying libp2p node.
	UpdateAllowList()

	// UpdateNodeAddresses fetches and updates the addresses of all the staked participants
	// in the Flow protocol.
	UpdateNodeAddresses()
}

// BlockExchange represents a block exchange, which is an abstraction over an underlying
// Bitswap network.
type BlockExchange interface {
	// BlockExchange embeds the BlockExchangeFetcher for convenience.
	// Each call to GetBlocks creates a new session under the hood.
	BlockExchangeFetcher

	// GetSession returns a session for requesting related blocks
	GetSession(ctx context.Context) BlockExchangeFetcher

	// HasBlock notifies the BlockExchange about the existence of a new block.
	// This may notify peers on the Bitswap network.
	HasBlock(block blocks.Block) error

	// Close closes the BlockExchange
	Close()
}

// BlockExchangeFetcher is an interface for requesting blocks from a BlockExchange.
type BlockExchangeFetcher interface {
	GetBlocks(cids ...cid.Cid) BlocksPromise
}

// Example
//
//  done, err := bex.GetBlocks(blocksToGet...).ForEach(func(b blocks.Block) {
//    // [...]
//  }).Send(ctx)
type BlocksPromise interface {
	// ForEach is used to register a callback to handle received blocks
	ForEach(cb func(blocks.Block)) BlocksRequest
}

// BlocksRequest represents a request for blocks on the Bitswap network.
type BlocksRequest interface {
	// Send initiates the request
	Send(ctx context.Context) (<-chan struct{}, error)
}

// Overlay represents the interface that middleware uses to interact with the
// overlay network layer.
type Overlay interface {
	// Topology returns an identity list of nodes which this node should be directly connected to as peers
	Topology() (flow.IdentityList, error)

	// Identities returns a list of all Flow identities on the network
	Identities() flow.IdentityList

	// GetIdentity returns the Identity associated with the given peer ID, if it exists
	Identity(peer.ID) (*flow.Identity, bool)

	Receive(nodeID flow.Identifier, msg *message.Message) error
}

// Connection represents an interface to read from & write to a connection.
type Connection interface {
	Send(msg interface{}) error
	Receive() (interface{}, error)
}
